package com.gdteam.kernel.persistence.impl;

import java.io.File;
import java.util.Properties;

import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Environment;
import org.slf4j.Logger;

import com.gdteam.kernel.domain.Domain;
import com.gdteam.kernel.domain.DomainModelDAO;
import com.gdteam.kernel.domain.DomainModelDAOFactory;
import com.gdteam.kernel.main.ServiceManager;
import com.gdteam.kernel.persistence.EntityProvider;
import com.gdteam.kernel.persistence.PersistencyUnitManager;

@EagerLoad
public class DefaultPersistencyUnitManager implements PersistencyUnitManager {

    private SessionFactory sf;
    private Logger log ;
    private DomainModelDAOFactory daofactory = null;
    private boolean started = false;
     
    private String dbFilePath;
    
    private EntityProvider ep;
    
    private ThreadLocal<Transaction> txtl = new ThreadLocal<Transaction>();
    
    private final Thread thread = new Thread(new Runnable(){
        public void run() {
            shutDownPersistenceUnit();
        }
    });
    
    public DefaultPersistencyUnitManager(@Inject ServiceManager mgr,
                                         @Inject DomainModelDAOFactory daofactory,
                                         Logger log,
                                         @Inject EntityProvider ep,
                                         @Inject @Symbol(com.gdteam.kernel.conf.Environment.DB_FILE_NAME) String dbname,
                                         @Inject @Symbol(com.gdteam.kernel.conf.Environment.GDTEAM_HOME) String gdteamhome) {
        mgr.registerOperations(this);
        
        this.log = log;
        this.daofactory = daofactory;
        this.ep = ep;
        this.dbFilePath  = new File(gdteamhome,dbname).getAbsolutePath(); 
        
        log.info("Db File path : " +this.dbFilePath);
        
    }

    public void delete(Domain model) {
        DomainModelDAO dao = getDAO(model.getFQN()); 
        dao.delete(model);
    }

    public Domain get(Domain model) {
        return ((DomainModelDAO)getDAO(model.getFQN())).get(model.getClass(), model.getId(), false);
    }

    public Domain get(Domain model, Boolean readonly) {
        return ((DomainModelDAO)getDAO(model.getFQN())).get(model.getClass(), model.getId(), readonly);
    }

    public DomainModelDAO<? extends Domain> getDAO(String fqn) {
        return this.daofactory.get(fqn);
    }

    public SessionFactory getFactory() {
        return this.sf;
    }

    public Session getUtil() {
        return this.sf.getCurrentSession();
    }

    public void save(Domain model) {
        getDAO(model.getFQN()).save(model);
    }

    public void update(Domain model) {
        getDAO(model.getFQN()).update(model);
    }
    
    public void beginTx() {
        try {
            Transaction tx = txtl.get(); 
            if (null == tx || !tx.isActive()) {
                tx = this.sf.getCurrentSession().beginTransaction();
                txtl.set(tx);
            }
        } catch (Throwable t) {
            rollBackTx();
            throw new RuntimeException(t);
        }
    }
    
    
    public void commitTx() {
        try {
            Transaction tx = txtl.get(); 
            if (null != tx && tx.isActive()) {
                tx.commit();
            }
        } catch (Throwable t) {
            rollBackTx();
            throw new RuntimeException(t);
        } finally {
            txtl.set(null);
        }
    }
    
    public void rollBackTx() {
        try {
            Transaction tx = txtl.get(); 
            if (null != tx && tx.isActive()) {
                tx.rollback();
            }
        } catch (Throwable t) {
            log.error(t.getMessage());
            throw new RuntimeException(t);
        } finally {
            txtl.set(null);
        }
    }

    public String getName() {
        return PersistencyUnitManager.FQN;
    }

    public void start() {
        if (!started) {
            AnnotationConfiguration cfg = new AnnotationConfiguration();
            Properties props = new Properties();
            props.put(Environment.HBM2DDL_AUTO, "update");
            props.put(Environment.DIALECT, "org.hibernate.dialect.HSQLDialect");
            props.put(Environment.DRIVER, "org.hsqldb.jdbcDriver");
            props.put(Environment.URL,"jdbc:hsqldb:file:" + dbFilePath);
            props.put(Environment.USER, "sa");
            props.put(Environment.PASS, "");
            
            
            props.put(Environment.SHOW_SQL, "false");
            props.put(Environment.FORMAT_SQL, "false");     
            props.put(Environment.ORDER_UPDATES, "true");
            props.put(Environment.STATEMENT_BATCH_SIZE, "10");
            props.put(Environment.USE_SQL_COMMENTS,"true");
            props.put(Environment.GENERATE_STATISTICS, "false");
            props.put(Environment.USE_REFLECTION_OPTIMIZER, "true");
            props.put(Environment.USE_GET_GENERATED_KEYS, "false");
            props.put(Environment.USE_IDENTIFIER_ROLLBACK, "false");
            props.put(Environment.AUTO_CLOSE_SESSION, "false");
            props.put(Environment.AUTOCOMMIT, "false");
            props.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread");

            cfg.addProperties(props);
            
            for(Class cl: ep.getEntities()) {
                cfg.addAnnotatedClass(cl);
            }
            
            cfg.configure();
            
            sf = cfg.buildSessionFactory();
            
            Runtime.getRuntime().addShutdownHook(this.thread);
            
            this.started = true;
        } 
    }

    public void stop() {

        if (started) {
            this.sf.close();
            this.txtl.remove();
            this.sf = null;
            this.started = false;
        }
        
    }
    
    public boolean isStarted() {
        return this.started;
    }
    
    private void shutDownPersistenceUnit() {
        try {
            beginTx();
            this.sf.getCurrentSession().createSQLQuery("SHUTDOWN COMPACT").executeUpdate();
            commitTx();
        } catch (Throwable t) {
            //t.printStackTrace();
            try {
                rollBackTx();
            } catch (Throwable t2) {
                //t.printStackTrace();
            }
        }
    }
}